Introduction
It is becoming more and more accepted that passwords alone
are not enough to secure an application. There is a need for "two-factor
authentication" to more adequately secure sensitive data. Two-factor
authentication is the notion that in addition to a password, some physical
identifier must be presented that assists in identifying a user to a system.
Typical implementations of two-factor authentication feature a physical token
or a phone-based app that generated a unique serial number every 60 seconds.
Fortunately, Plantronics new Legend UC headset adapter comes
with a unique serial number that identifies it. With this additional piece of
information, we can use this as part of this authentication mechanism. In this
article, I am going to show you a proof-of-concept code segment that
demonstrates of how you can use the serial number of a Bluetooth headset adapter
to assist in providing an authentication mechanism to a web application. This is
not a completely secure implementation, and there are ways to enhance the
implementation, but I will leave those exercises to the security experts.
Project Setup
In this sample, I will start with an ASP.Net Empty Web
Application. In order to use the Plantronics Spokes client-side library, we
need to add jQuery to the application. I can add jQuery easily by running the
following command in the package manager console:
Install-Package jQuery
With jQuery installed, I also need to add a reference to the
spokes.js file. You can get a copy of this script in the Plantronics SDK
‘Zeus’ sample. If you installed the SDK, the spokes.js file is located at: C:\Program
Files (x86)\Plantronics\Plantronics SDK\Samples\Zeus
I have assembled a very simple login box on a default.html
file that looks like the following:
This simple box allows the user to key in their userid
,
password
, and their current authenticator value. In my hypothetical scenario,
we will automatically complete the authenticator box with the serial number
value from the headset. The HTML for this box looks like the following:
<fieldset id="loginBox">
<legend>Login:</legend>
<form method="post" action="/account/login.aspx">
<b>User Id:</b><input type="text" id="userId" name="userId" />
<br />
<b>Password:</b><input type="password" name="pass" />
<br />
<b>Authenticator:</b><input type="text" id="authenticator" name="authenticator" />
<br />
<input type="submit" value="Login" name="text" id="btnLogin" />
</form>
</fieldset>
Activating the Form
To activate the interaction with the Plantronics adapter, we
need to start adding some JavaScript references and building a script to
communicate with the headset services. First, I will add script references to
the spokes.js and jquery.js files in this HTML file. Next, I will create and
add a reference to a new script file called auth.js that will contain all of my
logic to perform authentication.
Pro-tip: Create a reference at the top of any
JavaScript file to allow Visual Studio 2010 and 2012 intellisense to identify
the script content you are working with and present helpful tooltips to you
while you code. Simply create a reference comment at the top of your file in
the format:
And Visual Studio make the references available to you.
For the purposes of this form, I am going to write a full
JavaScript object that will maintain all information. I’ll start that
construct with a self-executing anonymous function structure like the
following:
var auth = (function () {
})();
This will define a new object, remember that functions are
objects in JavaScript, and assign it to the globally scoped auth variable.
Inside of the function-object, I will define a constructor that will begin
polling the adapter for information about the device and state changes.
var auth = (function () {
var pollingInterval = 5000;
var Auth = (function () {
var spokes = new Spokes("http://127.0.0.1:32001/Spokes");
var attached = false;
function Auth() {
ConnectSpokes();
setInterval(function () {
ConnectSpokes();
}, pollingInterval);
}
Auth.prototype.devicePresent = false;
Auth.prototype.serialNumber = "NOT SET";
return Auth;
})();
return new Auth();
})();
Several things are going on here: first, a private scope
variable called pollingInterval
is declared and is set with a 5000ms interval.
Next, an internal object is created with private scope variables for the Spokes
object and to also remember if we have properly connected to a device. Next a
constructor is defined that will attempt to connect to the adapter using the
Spokes (function to come) and then sets up a periodic poll to maintain that
connection.
It is important that we periodically poll for the state of
the Spokes connection because the Spokes JavaScript library will not inform us if
we are disconnected from the device. Instead, the library publishes events to
an internal queue that we must poll. There is no way to see the current state
of the adapter, you must remember what the last event raised was and behave
accordingly. By taking these steps, we can automatically format and reformat
the login box appropriately as the state of the device changes.
The ConnectSpokes
function that performs this polling makes
use of a call to the Spokes Device object. Here is the source for that function:
function ConnectSpokes () {
spokes.Device.deviceList(function (result) {
if (result.isError) {
console.log("Unable to connect to headset");
SetLoginBox(false);
}
else if (result.Result[0] == null) {
console.log("Error - Is there a headset connected?");
SetLoginBox(false);
}
else {
console.log("Connected to the headset adapter");
Auth.prototype.devicePresent = true;
Auth.prototype.serialNumber = result.Result[0].SerialNumber;
SetLoginBox(true)
}
});
};
The Spokes library exposes a deviceList
method that takes a
callback function to handle the results of the request to the hardware. This
callback is executed asynchronously, so the ConnectSpokes
function actually and
returns control very quickly, but is not completed executing until some time
later. The result object that is passed in to this method is inspected to
determine if there are any errors in connecting to the adapter, and to log
appropriate messages to the console. You could easily change this to perform
some other action if necessary, in this sample I have it call setLoginBox
with
a false argument. If we do have a successful connection to the device, I log
it appropriately and set two static methods on the Auth
object using the
Auth.prototype
syntax. These two variables indicate that there is a
devicePresent
and the serial number of the device. Finally, in the case that
we did connect properly to the headset adapter, I call SetLoginBox
with a true
argument.
The SetLoginBox
function is an internal function that uses
some jQuery DOM manipulation to configure the authenticator box and to
intercept the login button’s click action to set the authenticator value from
the serial number on the headset adapter:
function SetLoginBox(headsetPresent) {
if ($("#loginBox")[0]) {
if (headsetPresent) {
if (!attached) {
$("#authenticator").attr("disabled", true).css({ backgroundColor: "silver", color: "red", fontWeight: "bold", textAlign: "center" }).val("HEADSET");
$("#btnLogin").click(function () {
$("FORM").append("<input type='hidden' value='" + auth.serialNumber + "' name='authenticator'/>");
})
attached = true
}
} else {
if (attached) {
$("#authenticator").removeAttr("disabled").css({ backgroundColor: "transparent", color: "inherit", fontWeight: "inherit", textAlign: "inherit" }).val("");
$("#btnLogin").unbind("click");
attached = false;
}
}
}
};
I wrap the execution of this method in a test for the
loginBox
object. By taking this defensive coding step, I can add this code to
any page on the application that the loginBox
may appear on. Finally, I needed
to wrap the checks and formatting for the HTML objects in a check on the class
scoped attached variable. Without this check, this method would attach
multiple click event handlers to the btnLogin
object. This would create a
nasty memory leak if this method had executed hundreds of times, and had
hundreds of handlers hooked up to the click of the login button.
The resulting format of this method when the headset adapter
is present looks like the following figure:
This is a simple implementation, but the concepts are
sound: you can use a bluetooth device to provide authenticator capabilities.
Download the source code for this article
and the Plantronics SDK
to give it a try. I’m sure you’ll find some great capabilities that you can
use in your applications